<?php
/**
 * Talents come from diligence, and knowledge is gained by accumulation.
 *
 * @author: 晋<657306123@qq.com>
 * @date: 2019/6/26 20:48
 */

namespace Xin\Filesystem\Adapter;

use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\CanOverwriteFiles;
use League\Flysystem\Config;
use Xin\Filesystem\AdapterInterface;
use Xin\Filesystem\FilesystemException;

/**
 * 抽象云存储适配器
 */
abstract class AbstractCloudAdapter extends AbstractAdapter
	implements AdapterInterface, CanOverwriteFiles{

	/**
	 * 配置信息
	 *
	 * @var array
	 */
	protected $config = [];

	/**
	 * 文件信息缓存器
	 *
	 * @var \Xin\Filesystem\Adapter\CacheFileInfoPool
	 */
	protected $fileInfoPool;

	/**
	 * @var mixed
	 */
	protected $lastError;

	/**
	 * @var array
	 */
	protected static $resultOptionsMap = [];

	/**
	 * @var array
	 */
	protected static $mappingOptions = [];

	/**
	 * AbstractAdapter constructor.
	 *
	 * @param array $config
	 * @throws FilesystemException
	 */
	public function __construct(array $config){
		if(!isset($config['ak']) || empty($config['ak'])){
			throw new FilesystemException('ak 必须填写！');
		}

		if(!isset($config['sk']) || empty($config['sk'])){
			throw new FilesystemException('sk 必须填写！');
		}

		if(!isset($config['bucket']) || empty($config['bucket'])){
			throw new FilesystemException('bucket 必须填写！');
		}

		$this->config = $config;
		$this->fileInfoPool = new CacheFileInfoPool();
	}

	/**
	 * @return mixed
	 */
	public function getLastError(){
		return $this->lastError;
	}

	/**
	 * 读取资源
	 *
	 * @param resource $resource
	 * @return string
	 */
	protected function readResource($resource){
		$contents = '';

		while(!feof($resource)){
			$contents .= fread($resource, 1024);
		}

		return $contents;
	}

	/**
	 * @inheritDoc
	 */
	public function writeStream($path, $resource, Config $config){
		$contents = stream_get_contents($resource);
		return $this->write($path, $contents, $config);
	}

	/**
	 * @inheritDoc
	 */
	public function update($path, $contents, Config $config){
		return $this->write($path, $contents, $config);
	}

	/**
	 * @inheritDoc
	 */
	public function updateStream($path, $resource, Config $config){
		return $this->writeStream($path, $resource, $config);
	}

	/**
	 * @inheritDoc
	 */
	public function read($path){
		$object = $this->applyPathPrefix($path);

		$contents = file_get_contents($this->resolveUrl($object));

		return [
			'type'     => 'file',
			'contents' => $contents,
			'path'     => $path,
		];
	}

	/**
	 * @inheritDoc
	 */
	public function readStream($path){
		$object = $this->applyPathPrefix($path);

		if(ini_get('allow_url_fopen')){
			$stream = fopen($this->resolveUrl($object), 'r');
			return [
				'stream' => $stream,
				'path'   => $path,
			];
		}

		return false;
	}

	/**
	 * @inheritDoc
	 */
	public function rename($path, $newpath){
		if(!$this->copy($path, $newpath)){
			return false;
		}

		return $this->delete($path);
	}

	/**
	 * @inheritDoc
	 */
	public function has($path){
		return $this->getMetadata($path) != null;
	}

	/**
	 * @inheritDoc
	 */
	public function getSize($path){
		return $this->getMetadata($path);
	}

	/**
	 * @inheritDoc
	 */
	public function getMimetype($path){
		return $this->getMetadata($path);
	}

	/**
	 * @inheritDoc
	 */
	public function getTimestamp($path){
		return $this->getMetadata($path);
	}

	/**
	 * Make resource url.
	 *
	 * @param string $path
	 * @return string
	 */
	public function resolveUrl($path){
		$segments = $this->parseUrl($path);
		$query = empty($segments['query']) ? '' : '?'.$segments['query'];

		$path = ltrim(implode('/', array_map('rawurlencode', explode('/', $segments['path']))), '/');

		return $this->normalizeHost($this->config['domain']).$path.$query;
	}

	/**
	 * @param mixed $stat
	 * @return array 优化文件信息
	 */
	abstract protected function normalizeFileInfo($stat);

	/**
	 * @param string $domain
	 * @return string
	 */
	protected function normalizeHost($domain){
		if(0 !== stripos($domain, 'https://') && 0 !== stripos($domain, 'http://')){
			$domain = "http://{$domain}";
		}

		return rtrim($domain, '/').'/';
	}

	/**
	 * Does a UTF-8 safe version of PHP parse_url function.
	 *
	 * @param string $url URL to parse
	 * @return mixed associative array or false if badly formed URL
	 * @see     http://us3.php.net/manual/en/function.parse-url.php
	 */
	protected static function parseUrl($url){
		$result = false;

		// Build arrays of values we need to decode before parsing
		$entities = [
			'%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23',
			'%5B', '%5D', '%5C',
		];
		$replacements = ['!', '*', "'", '(', ')', ';', ':', '@', '&', '=', '$', ',', '/', '?', '#', '[', ']', '/'];

		// Create encoded URL with special URL characters decoded so it can be parsed
		// All other characters will be encoded
		$encodedURL = str_replace($entities, $replacements, urlencode($url));

		// Parse the encoded URL
		$encodedParts = parse_url($encodedURL);

		// Now, decode each value of the resulting array
		if($encodedParts){
			foreach($encodedParts as $key => $value){
				$result[$key] = urldecode(str_replace($replacements, $entities, $value));
			}
		}

		return $result;
	}

	/**
	 * @param \League\Flysystem\Config $config
	 * @return array
	 */
	protected function resolveOptions(Config $config){
		$options = $this->config;

		foreach(static::$mappingOptions as $option => $ossOption){
			if(!$config->has($option)){
				continue;
			}

			$options[$ossOption] = $config->get($option);
		}

		return $options;
	}
}
